#![allow(dead_code)]

use crate::porting::printf;
use crate::sync::mutex::Mutex;
use crate::util::{cstr_to_str, cstr_with_len_to_str};
use crate::{cstr, cstr_ptr};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use core::ffi::{c_char, c_int, c_uint};
use core::fmt::Arguments;

trait LoggerWrite: Sync + Send {
    fn write_fmt(
        &mut self,
        level: u8,
        module: &str,
        line: u32,
        args: core::fmt::Arguments,
        nl: bool,
    ) -> core::fmt::Result;
}

// These constants are used by the macros but we don't want to expose
// them to library users.
#[doc(hidden)]
pub const LIBC_NEWLINE: &str = "\n";
#[doc(hidden)]
pub const LIBC_STDOUT: u32 = 1;
#[doc(hidden)]
pub const LIBC_STDERR: u32 = 2;

pub const LOG_LEVEL_OFF: u8 = 0;
pub const LOG_LEVEL_DEBUG: u8 = 1;
pub const LOG_LEVEL_INFO: u8 = 2;
pub const LOG_LEVEL_WARN: u8 = 3;
pub const LOG_LEVEL_ERROR: u8 = 4;
pub const LOG_LEVEL_PASS: u8 = 5;

#[doc(hidden)]
pub struct LIBCWriter(u32);

impl core::fmt::Write for LIBCWriter {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        libc_println(self.0, s)
    }
}

impl LIBCWriter {
    pub fn new(handle: u32) -> LIBCWriter {
        LIBCWriter(handle)
    }
    pub fn write_str(&mut self, s: &str) -> core::fmt::Result {
        libc_println(self.0, s)
    }
    pub fn write_nl(&mut self) -> core::fmt::Result {
        libc_println(self.0, LIBC_NEWLINE)
    }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct timezone {
    pub tz_minuteswest: libc::c_int,
    pub tz_dsttime: libc::c_int,
}

impl LoggerWrite for LIBCWriter {
    fn write_fmt(
        &mut self,
        level: u8,
        module: &str,
        line: u32,
        args: Arguments,
        nl: bool,
    ) -> core::fmt::Result {
        unsafe {
            if level < LEVEL {
                return Ok(());
            }
        }
        let level_name = match level {
            LOG_LEVEL_DEBUG => "D",
            LOG_LEVEL_INFO => "I",
            LOG_LEVEL_WARN => "W",
            LOG_LEVEL_ERROR => "E",
            LOG_LEVEL_PASS => "P",
            _ => return Ok(()),
        };
        let (pc, sc) = if unsafe { ENABLE_COLOR } {
            match level {
                LOG_LEVEL_INFO => ("\x1b[1;33m", "\x1b[0m"),
                LOG_LEVEL_WARN => ("\x1b[1;31m", "\x1b[0m"),
                LOG_LEVEL_ERROR => ("\x1b[1;91m", "\x1b[0m"),
                LOG_LEVEL_PASS => ("\x1b[1;32m", "\x1b[0m"),
                LOG_LEVEL_DEBUG | _ => ("", ""),
            }
        } else {
            ("", "")
        };

        unsafe {
            let mut tv: libc::timeval = core::mem::zeroed();
            libc::gettimeofday(&mut tv, core::ptr::null_mut());
            let mut t: libc::tm = core::mem::zeroed();
            libc::localtime_r(&(tv.tv_sec as libc::time_t), &mut t);
            let _ = core::fmt::Write::write_fmt(
                self,
                format_args!(
                    "{}[{}] <{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}> [{}:{}] ",
                    pc,
                    level_name,
                    t.tm_year + 1900,
                    t.tm_mon + 1,
                    t.tm_mday,
                    t.tm_hour,
                    t.tm_min,
                    t.tm_sec,
                    tv.tv_usec / 1000,
                    module,
                    line
                ),
            );
        }
        let _ = core::fmt::Write::write_fmt(self, args);
        if !sc.is_empty() {
            let _ = self.write_str(sc);
        }
        if nl {
            self.write_nl()
        } else {
            Ok(())
        }
    }
}

#[cfg(not(windows))]
#[doc(hidden)]
#[inline]
pub fn libc_println(_handle: u32, msg: &str) -> core::fmt::Result {
    unsafe {
        let mut pos = 0;
        while pos < msg.len() {
            let left = msg.len() - pos;
            let len = if left > BUFFER.len() - 1 {
                BUFFER.len() - 1
            } else {
                left
            };
            core::ptr::copy((&msg.as_bytes()[pos..]).as_ptr(), BUFFER.as_mut_ptr(), len);
            BUFFER[len] = 0;
            printf(cstr!(BUFFER));
            pos += len;
        }
        Ok(())
    }
}

struct ExternLogger {
    output:
        unsafe extern "C" fn(level: c_int, func: *const c_char, line: u32, fmt: *const c_char, ...),
    pos: usize,
}

unsafe impl Send for ExternLogger {}
unsafe impl Sync for ExternLogger {}

impl LoggerWrite for ExternLogger {
    fn write_fmt(
        &mut self,
        level: u8,
        module: &str,
        line: u32,
        args: core::fmt::Arguments,
        nl: bool,
    ) -> core::fmt::Result {
        unsafe {
            if level < LEVEL {
                return Ok(());
            }
        }
        let _ = core::fmt::write(self, args);
        if nl {
            let _ = core::fmt::write(self, format_args!("\n"));
        }
        let mut m = module.to_owned();
        m.push_str("\0");
        unsafe {
            BUFFER[self.pos] = 0;
            (self.output)(
                level as c_int,
                m.as_ptr() as *const c_char,
                line,
                BUFFER.as_ptr() as *const c_char,
            );
        }
        self.pos = 0;
        Ok(())
    }
}

impl core::fmt::Write for ExternLogger {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        unsafe {
            let left = if self.pos + s.len() >= BUFFER.len() - 1 {
                BUFFER.len() - 1 - self.pos
            } else {
                s.len()
            };
            if left > 0 {
                core::ptr::copy(
                    (&s.as_bytes()[0..left]).as_ptr(),
                    (&mut BUFFER[self.pos..]).as_mut_ptr(),
                    left,
                );
                self.pos += left;
            }
        }
        Ok(())
    }
}

static mut BUFFER: [u8; 512] = [0u8; 512];
static mut LEVEL: u8 = LOG_LEVEL_INFO;

// 0(off) 1(debug) 2(info) 3(warn) 4(error)
pub fn set_log_level(level: u8) {
    crate::sys_logln!(LOG_LEVEL_WARN, "rust std log level {}", level);
    unsafe {
        LEVEL = level;
    }
    #[cfg(feature = "f-log")]
    {
        use crate::loglib::from_u8_for_level_filter;
        log::set_max_level(from_u8_for_level_filter(level));
    }
}

pub fn get_log_level() -> u8 {
    unsafe { LEVEL }
}

#[no_mangle]
pub unsafe extern "C" fn es_detect_env_log_level() {
    let v = libc::getenv(cstr_ptr!("ES_LOG"));
    if !v.is_null() {
        let s = cstr_to_str(v);
        let level = match s {
            "off" => LOG_LEVEL_OFF,
            "debug" => LOG_LEVEL_DEBUG,
            "info" => LOG_LEVEL_INFO,
            "warn" => LOG_LEVEL_WARN,
            "error" => LOG_LEVEL_ERROR,
            "pass" => LOG_LEVEL_PASS,
            _ => LOG_LEVEL_INFO,
        };
        set_log_level(level);
    }
    let c = libc::getenv(cstr_ptr!("ES_LOG_COLOR"));
    if !c.is_null() {
        let s = cstr_to_str(c);
        if s == "1" || s == "on" {
            ENABLE_COLOR = true;
        }
    }
}

pub fn detect_env_log_level() {
    unsafe { es_detect_env_log_level() };
}

#[no_mangle]
pub extern "C" fn set_iot_rust_log_level(level: u8) {
    set_log_level(level)
}

#[no_mangle]
pub extern "C" fn get_iot_rust_log_level() -> u8 {
    unsafe { LEVEL }
}

#[no_mangle]
pub extern "C" fn set_iot_rust_log(
    func: unsafe extern "C" fn(
        level: c_int,
        func: *const c_char,
        line: u32,
        fmt: *const c_char,
        ...
    ),
) {
    let mut lock = LOGGER.lock().unwrap();
    *lock = Some(Box::new(ExternLogger {
        output: func,
        pos: 0,
    }));
}

static mut ENABLE_COLOR: bool = false;

#[no_mangle]
unsafe extern "C" fn es_enable_log_color(enable: u8) {
    ENABLE_COLOR = if enable != 0 { true } else { false };
}

#[no_mangle]
unsafe extern "C" fn write_log_rust(
    level: u8,
    module: *const c_char,
    module_len: c_uint,
    line: c_uint,
    data: *const c_char,
    data_len: c_uint,
) {
    if level < LEVEL || LEVEL == LOG_LEVEL_OFF {
        return;
    }
    let s = if !module.is_null() {
        cstr_with_len_to_str(module, module_len as usize)
    } else {
        ""
    };
    let d = if !data.is_null() {
        cstr_with_len_to_str(data, data_len as usize)
    } else {
        ""
    };
    write_log(level, s, line, format_args!("{}", d), true);
}

lazy_static! {
    static ref LOGGER: Mutex<Option<Box<dyn LoggerWrite>>> = Mutex::new(None);
}

pub fn write_log(level: u8, module: &str, line: u32, args: core::fmt::Arguments, nl: bool) {
    unsafe {
        if level < LEVEL || LEVEL == LOG_LEVEL_OFF {
            return;
        }
    }
    let mut lock = LOGGER.lock().unwrap();
    if lock.is_none() {
        *lock = Some(Box::new(LIBCWriter::new(LIBC_STDOUT)));
    }
    let _ = lock
        .as_mut()
        .unwrap()
        .write_fmt(level, module, line, args, nl);
}

#[macro_export]
macro_rules! sys_logln {
    ($level:expr, $($arg:tt)*) => {
        #[allow(unused_must_use)]
        {
            $crate::sys_log::write_log($level, module_path!(), line!(), format_args!($($arg)*), true);
        }
    };
}

#[macro_export]
macro_rules! sys_log {
    ($level:expr, $($arg:tt)*) => {
        #[allow(unused_must_use)]
        {
            $crate::sys_log::write_log($level, module_path!(), line!(), format_args!($($arg)*), false);
        }
    };
}

#[macro_export]
macro_rules! debugln {
    ($($arg:tt)*) => {
        $crate::sys_logln!($crate::sys_log::LOG_LEVEL_DEBUG, $($arg)*);
    };
}

#[macro_export]
macro_rules! debug {
    ($($arg:tt)*) => {
        $crate::sys_log!($crate::sys_log::LOG_LEVEL_DEBUG, $($arg)*);
    };
}

#[macro_export]
macro_rules! infoln {
    ($($arg:tt)*) => {
        $crate::sys_logln!($crate::sys_log::LOG_LEVEL_INFO, $($arg)*);
    };
}

#[macro_export]
macro_rules! info {
    ($($arg:tt)*) => {
        $crate::sys_log!($crate::sys_log::LOG_LEVEL_INFO, $($arg)*);
    };
}

#[macro_export]
macro_rules! warnln {
    ($($arg:tt)*) => {
        $crate::sys_logln!($crate::sys_log::LOG_LEVEL_WARN, $($arg)*);
    };
}

#[macro_export]
macro_rules! warn {
    ($($arg:tt)*) => {
        $crate::sys_log!($crate::sys_log::LOG_LEVEL_WARN, $($arg)*);
    };
}

#[macro_export]
macro_rules! errorln {
    ($($arg:tt)*) => {
        $crate::sys_logln!($crate::sys_log::LOG_LEVEL_ERROR, $($arg)*);
    };
}

#[macro_export]
macro_rules! error {
    ($($arg:tt)*) => {
        $crate::sys_log!($crate::sys_log::LOG_LEVEL_ERROR, $($arg)*);
    };
}

pub mod std_name {
    #[macro_export]
    macro_rules! print {
        ($($arg:tt)*) => {
            $crate::info!($($arg)*);
        };
    }
    #[macro_export]
    macro_rules! println {
        ($($arg:tt)*) => {
            $crate::infoln!($($arg)*);
        };
    }
    #[macro_export]
    macro_rules! eprint {
        ($($arg:tt)*) => {
            $crate::error!($($arg)*);
        };
    }
    #[macro_export]
    macro_rules! eprintln {
        ($($arg:tt)*) => {
            $crate::errorln!($($arg)*);
        };
    }
}
